/*********************************************************************
 *
 *	Medium Access Control (MAC) Layer for Microchip PIC18F97J60 family
 *  Module for Microchip TCP/IP Stack
 *	 -Provides access to PIC18F97J60 family Ethernet controller
 *	 -Reference: PIC18F97J60 Family data sheet, IEEE 802.3 Standard
 *
 *********************************************************************
 * FileName:        ETH97J60.c
 * Dependencies:    ETH97J60.h
 *					MAC.h
 *					string.h
 *                  StackTsk.h
 *                  Helpers.h
 *					Delay.h
 * Processor:       PIC18F97J60 Family
 * Compiler:        Microchip C18 v3.30 or higher
 *					HI-TECH PICC-18 PRO 9.63PL2 or higher
 * Company:         Microchip Technology, Inc.
 *
 * Software License Agreement
 *
 * Copyright (C) 2002-2009 Microchip Technology Inc.  All rights
 * reserved.
 *
 * Microchip licenses to you the right to use, modify, copy, and
 * distribute:
 * (i)  the Software when embedded on a Microchip microcontroller or
 *      digital signal controller product ("Device") which is
 *      integrated into Licensee's product; or
 * (ii) ONLY the Software driver source files ENC28J60.c, ENC28J60.h,
 *		ENCX24J600.c and ENCX24J600.h ported to a non-Microchip device
 *		used in conjunction with a Microchip ethernet controller for
 *		the sole purpose of interfacing with the ethernet controller.
 *
 * You should refer to the license agreement accompanying this
 * Software for additional information regarding your rights and
 * obligations.
 *
 * THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT
 * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
 * LIMITATION, ANY WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF
 * PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR SERVICES, ANY CLAIMS
 * BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE
 * THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, OR OTHER
 * SIMILAR COSTS, WHETHER ASSERTED ON THE BASIS OF CONTRACT, TORT
 * (INCLUDING NEGLIGENCE), BREACH OF WARRANTY, OR OTHERWISE. *
 * Author               Date   	 Comment
 *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * Rawin Rojvanit       07/26/05 Stuff
 * Howard Schlunder     11/17/05 Ported to PIC18F97J60
 * Howard Schlunder	06/16/06 Synchronized with ENC28J60 code
 * Howard Schlunder	05/21/07 Fixed a TX lockup problem
 * Marc Lobelle         03/13/10 updated to support SDCC opensource compiler
********************************************************************/
#define __ETH97J60_C
#define __18F97J60  //ML
#define __SDCC__ //ML
#include "../Include/HardwareProfile.h"


// Make sure that this hardware profile has a PIC18F97J60 family device in it
#if (defined(__18F97J60) || defined(__18F96J65) || defined(__18F96J60) || \
   defined(__18F87J60) || defined(__18F86J65) || defined(__18F86J60) || \
   defined(__18F67J60) || defined(__18F66J65) || defined(__18F66J60) || \
   defined(_18F97J60) ||  defined(_18F96J65) ||  defined(_18F96J60) || \
   defined(_18F87J60) ||  defined(_18F86J65) ||  defined(_18F86J60) ||  \
   defined(_18F67J60) ||  defined(_18F66J65) ||  defined(_18F66J60)) \
   && !defined(ENC_CS_TRIS) && !defined(ENC100_INTERFACE_MODE) && \
   !defined(ZG_CS_TRIS)

#include "../Include/TCPIP_Stack/TCPIP.h"


/** D E F I N I T I O N S ****************************************************/
// Since the Ethernet PHY doesn't support auto-negotiation, full-duplex mode is
// not compatible with most switches/routers.  If a dedicated network is used
// where the duplex of the remote node can be manually configured, you may
// change this configuration.  Otherwise, half duplex should always be used.
#define HALF_DUPLEX
//#define FULL_DUPLEX

// Pseudo Functions
#define LOW(a) 					(a & 0xFF)
#define HIGH(a) 				((a>>8) & 0xFF)

#define ETHER_IP	(0x00u)
#define ETHER_ARP	(0x06u)

// A header appended at the start of all RX frames by the hardware
typedef struct _ENC_PREAMBLE
{
    WORD			NextPacketPointer;
    RXSTATUS		StatusVector;

    MAC_ADDR        DestMACAddr;
    MAC_ADDR        SourceMACAddr;
    WORD_VAL        Type;
} ENC_PREAMBLE;


// Internal MAC level variables and flags.
static WORD_VAL NextPacketLocation;
static WORD_VAL CurrentPacketLocation;
static BOOL WasDiscarded;
static WORD wTXWatchdog;

void MACPrintHeader(BYTE x);

/******************************************************************************
 * Function:        void MACInit(void)
 *
 * PreCondition:    None
 *
 * Input:           None
 *
 * Output:          None
 *
 * Side Effects:    None
 *
 * Overview:        MACInit enables the Ethernet module, waits for the
 *                  to become ready, and programs all registers for future
 *                  TX/RX operations.
 *
 * Note:            This function blocks for at least 1ms, waiting for the
 *                  hardware to stabilize.
 *****************************************************************************/
void MACInit(void)
{
  //	BYTE i;

    TRISAbits.TRISA0 = 0; // Set LEDA as output (green on ethernet connector)
    TRISAbits.TRISA1 = 0; // Set LEDB as output (yellow on ethernet connector)
    ECON2bits.ETHEN = 1;    // Enable Ethernet!

    // Wait for PHYRDY to become set.
    while(!ESTATbits.PHYRDY);

    // Configure the receive buffer boundary pointers
    // and the buffer write protect pointer (receive buffer read pointer)
    WasDiscarded = TRUE;
    NextPacketLocation.Val = RXSTART;
    ERXSTL = LOW(RXSTART);
    ERXSTH = HIGH(RXSTART);

    ERXRDPTL = LOW(RXSTOP);	// Write low byte first
    ERXRDPTH = HIGH(RXSTOP);    // Write high byte last

    ERXNDL = LOW(RXSTOP);
    ERXNDH = HIGH(RXSTOP);

    ETXSTL = LOW(TXSTART);
    ETXSTH = HIGH(TXSTART);

    // Write a permanent per packet control byte of 0x00
    EWRPTL = LOW(TXSTART);
    EWRPTH = HIGH(TXSTART);
    EDATA = 0x00;
//	MACPut(0x00);

    // Configure Receive Filters
    // (No need to reconfigure - Unicast OR Broadcast with CRC checking is
    // acceptable)
    //ERXFCON = ERXFCON_CRCEN;     // Promiscious mode

    // Configure the MAC
    // Enable the receive portion of the MAC
    MACON1 = MACON1_TXPAUS | MACON1_RXPAUS | MACON1_MARXEN; Nop();

    // Pad packets to 60 bytes, add CRC, and check Type/Length field.
#if defined(FULL_DUPLEX)
    MACON3 = MACON3_PADCFG0 | MACON3_TXCRCEN | MACON3_FRMLNEN | MACON3_FULDPX; 
    Nop();
    MABBIPG = 0x15; Nop();
#else
    MACON3 = MACON3_PADCFG0 | MACON3_TXCRCEN | MACON3_FRMLNEN; Nop();
    MABBIPG = 0x12; Nop();
#endif

    // Allow infinite deferals if the medium is continuously busy
    // (do not time out a transmission if the half duplex medium is
    // completely saturated with other people's data)
    MACON4 = MACON4_DEFER; Nop();

	// Set non-back-to-back inter-packet gap to 9.6us.  The back-to-back
	// inter-packet gap (MABBIPG) is set by MACSetDuplex() which is called
	// later.
    MAIPGL = 0x12; Nop();
    MAIPGH = 0x0C; Nop();

	// Set the maximum packet size which the controller will accept
    MAMXFLL = LOW(6+6+2+1500+4); Nop();
    MAMXFLH = HIGH(6+6+2+1500+4); Nop();

    // Initialize physical MAC address registers
    MAADR1 = AppConfig.MyMACAddr.v[0]; Nop();
    MAADR2 = AppConfig.MyMACAddr.v[1]; Nop();
    MAADR3 = AppConfig.MyMACAddr.v[2]; Nop();
    MAADR4 = AppConfig.MyMACAddr.v[3]; Nop();
    MAADR5 = AppConfig.MyMACAddr.v[4]; Nop();
    MAADR6 = AppConfig.MyMACAddr.v[5]; Nop();

    // Disable half duplex loopback in PHY and set RXAPDIS bit as per errata
    WritePHYReg(PHCON2, PHCON2_HDLDIS | PHCON2_RXAPDIS);

    // Configure LEDA to display LINK status, LEDB to display TX/RX activity
    SetLEDConfig(0x3472);

    // Set the PHY into the proper duplex state
#if defined(FULL_DUPLEX)
    WritePHYReg(PHCON1, PHCON1_PDPXMD);
#else
    WritePHYReg(PHCON1, 0x0000);
#endif

    // Enable packet reception
    ECON1bits.RXEN = 1;

}//end MACInit


/******************************************************************************
 * Function:        BOOL MACIsLinked(void)
 *
 * PreCondition:    None
 *
 * Input:           None
 *
 * Output:          TRUE: If the PHY reports that a link partner is present
 *			  and the link has been up continuously since the last
 *			  call to MACIsLinked()
 *		    FALSE: If the PHY reports no link partner, or the link went
 *			   down momentarily since the last call to MACIsLinked()
 *
 * Side Effects:    None
 *
 * Overview:        Returns the PHSTAT1.LLSTAT bit.
 *
 * Note:            None
 *****************************************************************************/
BOOL MACIsLinked(void)
{
    PHYREG pr;
    // LLSTAT is a latching low link status bit.  Therefore, if the link
    // goes down and comes back up before a higher level stack program calls
    // MACIsLinked(), MACIsLinked() will still return FALSE.  The next
    // call to MACIsLinked() will return TRUE (unless the link goes down
    // again).

    pr.Val= ReadPHYReg(PHSTAT1);
    return pr.PHSTAT1bits.LLSTAT;

}


/******************************************************************************
 * Function:        BOOL MACIsTxReady(void)
 *
 * PreCondition:    None
 *
 * Input:           None
 *
 * Output:          TRUE: If no Ethernet transmission is in progress
 *		    FALSE: If a previous transmission was started, and it has
 *			   not completed yet.  While FALSE, the data in the
 *			   transmit buffer and the TXST/TXND pointers must not
 *			   be changed.
 *
 * Side Effects:    None
 *
 * Overview:        Returns the ECON1.TXRTS bit
 *
 * Note:            None
 *****************************************************************************/
BOOL MACIsTxReady(void)
{
    if(!ECON1bits.TXRTS)
	return TRUE;

    // Retry transmission if the current packet seems to be not completing
    // Wait 3ms before triggering the retry.
    if((WORD)TickGet() - wTXWatchdog >= (3ull*TICK_SECOND/1000ull))
    {
	ECON1bits.TXRTS = 0;
	MACFlush();
    }
    return FALSE;
}


/******************************************************************************
 * Function:        void MACDiscardRx(void)
 *
 * PreCondition:    None
 *
 * Input:           None
 *
 * Output:          None
 *
 * Side Effects:    None
 *
 * Overview:        Marks the last received packet (obtained using
 *		    MACGetHeader())as being processed and frees the buffer
 *		    memory associated with it
 *
 * Note:            Is is safe to call this function multiple times between
 *		    MACGetHeader() calls.  Extra packets won't be thrown away
 *		    until MACGetHeader() makes it available.
 *****************************************************************************/
void MACDiscardRx(void)
{
    WORD_VAL NewRXRDLocation;

    // Make sure the current packet was not already discarded
    if(WasDiscarded) return;
    WasDiscarded = TRUE;

    // Decrement the next packet pointer before writing it into
    // the ERXRDPT registers.  This is a silicon errata workaround.
    // RX buffer wrapping must be taken into account if the
    // NextPacketLocation is precisely RXSTART.
    NewRXRDLocation.Val = NextPacketLocation.Val - 1;
#if RXSTART == 0
    if(NewRXRDLocation.Val > RXSTOP)
#else
    if((NewRXRDLocation.Val < RXSTART) || ( NewRXRDLocation.Val > RXSTOP))
#endif
    {
	NewRXRDLocation.Val = RXSTOP;
    }

    // Decrement the RX packet counter register, EPKTCNT
    ECON2bits.PKTDEC = 1;

    // Move the receive read pointer to unwrite-protect the memory used by the
    // last packet.  The writing order is important: set the low byte first,
    // high byte last.
    ERXRDPTL = NewRXRDLocation.v[0];
    ERXRDPTH = NewRXRDLocation.v[1];

    // The PKTIF flag should automatically be cleared by hardware, but
    // early beta silicon requires that you manually clear it.  This should be
    // unneeded for production A0 silicon and later.
    EIRbits.PKTIF = 0;
}


/******************************************************************************
 * Function:        WORD MACGetFreeRxSize(void)
 *
 * PreCondition:    None
 *
 * Input:           None
 *
 * Output:          A WORD estimate of how much RX buffer space is free at
 *					the present time.
 *
 * Side Effects:    None
 *
 * Overview:        None
 *
 * Note:            None
 *****************************************************************************/
WORD MACGetFreeRxSize(void)
{
    WORD_VAL ReadPT, WritePT;

    // Read the Ethernet hardware buffer write pointer.  Because packets can be
    // received at any time, it can change between reading the low and high
    // bytes.  A loop is necessary to make certain a proper low/high byte pair
    // is read.
    do {
	// Save EPKTCNT in a temporary location
	ReadPT.v[0] = EPKTCNT;

	WritePT.Val = ((WORD)ERXWRPTH)<<8|ERXWRPTL;
    } while(EPKTCNT != ReadPT.v[0]);

    // Determine where the write protection pointer is
    ReadPT.Val = ERXRDPTH<<8|ERXRDPTL;


    // Calculate the difference between the pointers, taking care to account
    // for buffer wrapping conditions
    if(WritePT.Val > ReadPT.Val)
    {
	return (RXSTOP - RXSTART) - (WritePT.Val - ReadPT.Val);
    }
    else if(WritePT.Val == ReadPT.Val)
    {
	return RXSIZE - 1;
    }
    else
    {
	return ReadPT.Val - WritePT.Val - 1;
    }
}

/******************************************************************************
 * Function:        BOOL MACGetHeader(MAC_ADDR *remote, BYTE* type)
 *
 * PreCondition:    None
 *
 * Input:           *remote: Location to store the Source MAC address of the
 *			     received frame.
 *		    *type: Location of a BYTE to store the constant
 *			   AC_UNKNOWN, ETHER_IP, or ETHER_ARP, representing
 *			   the contents of the Ethernet type field.
 *
 * Output:          TRUE: If a packet was waiting in the RX buffer.  The
 *			  remote, and type values are updated.
 *		    FALSE: If a packet was not pending.  remote and type are
 *			   not changed.
 *
 * Side Effects:    Last packet is discarded if MACDiscardRx() hasn't already
 *					been called.
 *
 * Overview:        None
 *
 * Note:            None
 *****************************************************************************/
BOOL MACGetHeader(MAC_ADDR *remote, BYTE* type)
{
    ENC_PREAMBLE header;

    // Test if at least one packet has been received and is waiting
    if(EPKTCNT == 0u)
    {
        return FALSE;
    }

    // Make absolutely certain that any previous packet was discarded
    if(WasDiscarded == FALSE)
    {
	MACDiscardRx();
	return FALSE;
    }
    // Save the location of this packet
    CurrentPacketLocation.Val = NextPacketLocation.Val;

    // Set the read pointer to the beginning of the next unprocessed packet
    ERDPTH = HIGH(CurrentPacketLocation.Val);
    ERDPTL = LOW(CurrentPacketLocation.Val);

    // Obtain the MAC header from the Ethernet buffer
    MACGetArray((BYTE*)&header, sizeof(header));

    // The EtherType field, like most items transmitted on the Ethernet medium
    // are in big endian.
    header.Type.Val = swaps(header.Type.Val);

    // Do a sanity check.  There might be a bug in code someplace if this
    // Reset() ever happens.  Check for potential errors in array/pointer 
    // writing code.
    if(header.NextPacketPointer > RXSTOP || 
       ((BYTE_VAL*)(&header.NextPacketPointer))->bits.b0 ||
       header.StatusVector.bits.Zero ||
       header.StatusVector.bits.CRCError ||
       header.StatusVector.bits.ByteCount > 1518u ||
       !header.StatusVector.bits.ReceiveOk)
    {
        DisplayString(0,"error reading hdr");while(1);//////////////////////ML
	//		Reset(); //We shouldn't get here anyway
    }

    // Save the location where the hardware will write the next packet to
    NextPacketLocation.Val = header.NextPacketPointer;

    // Return the Ethernet frame's Source MAC address field to the caller
    // This parameter is useful for replying to requests without requiring an
    // ARP cycle.
    memcpy((void*)remote->v, (void*)header.SourceMACAddr.v, sizeof(*remote));

    // Return a simplified version of the EtherType field to the caller
    *type = MAC_UNKNOWN;
    if( (header.Type.v[1] == 0x08u) &&
    	((header.Type.v[0] == ETHER_IP) || (header.Type.v[0] == ETHER_ARP)) )
    {
    	*type = header.Type.v[0];
    }

    // Mark this packet as discardable
    WasDiscarded = FALSE;
    return TRUE;
}


/******************************************************************************
 * Function:       void MACPutHeader(MAC_ADDR *remote, BYTE type, WORD dataLen)
 *
 * PreCondition:   MACIsTxReady() must return TRUE.
 *
 * Input:          *remote: Pointer to memory which contains the destination
 * 			  MAC address (6 bytes)
 *		   type: The constant ETHER_ARP or ETHER_IP, defining which
 *			  value to write into the Ethernet header's type field.
 *			  dataLen: Length of the Ethernet data payload
 *
 * Output:          None
 *
 * Side Effects:    None
 *
 * Overview:        None
 *
 * Note:            Because of the dataLen parameter, it is probably
 *		    advantagous to call this function immediately before
 *		    transmitting a packet rather than initially when the
 *		    packet is first created.  The order in which the packet
 *		    is constructed (header first or data first) is not
 *		    important.
 *****************************************************************************/
void MACPutHeader(MAC_ADDR *remote, BYTE type, WORD dataLen)
{
    // Set the write pointer to the beginning of the transmit buffer
    EWRPTL = LOW(TXSTART + 1);
    EWRPTH = HIGH(TXSTART + 1);

    // Calculate where to put the TXND pointer
    dataLen += (WORD)sizeof(ETHER_HEADER) + TXSTART;

    // Write the TXND pointer into the registers, given the dataLen given
    ETXNDL = LOW(dataLen);
    ETXNDH = HIGH(dataLen);

    // Set the per-packet control byte and write the Ethernet destination
    // address
    MACPutArray((BYTE*)remote, sizeof(*remote));

    // Write our MAC address in the Ethernet source field
    MACPutArray((BYTE*)&AppConfig.MyMACAddr, sizeof(AppConfig.MyMACAddr));

    // Write the appropriate Ethernet Type WORD for the protocol being used
    MACPut(0x08);
    MACPut((type == MAC_IP) ? ETHER_IP : ETHER_ARP);
}

/******************************************************************************
 * Function:        void MACFlush(void)
 *
 * PreCondition:    A packet has been created by calling MACPut() and
 *		    MACPutHeader().
 *
 * Input:           None
 *
 * Output:          None
 *
 * Side Effects:    None
 *
 * Overview:        MACFlush causes the current TX packet to be sent out on
 *		    the Ethernet medium.  The hardware MAC will take control
 *		    and handle CRC generation, collision retransmission and
 *		    other details.
 *
 * Note:	    After transmission completes (MACIsTxReady() returns TRUE),
 *		    the packet can be modified and transmitted again by calling
 *		    MACFlush() again.  Until MACPutHeader() or MACPut() is
 *		    called (in the TX data area), the data in the TX buffer
 *		    will not be corrupted.
 *****************************************************************************/
void MACFlush(void)
{

    // Reset the Ethernet TX logic.  This is an errata workaround to
    // prevent the TXRTS bit from getting stuck set indefinitely, 
    // causing the stack to lock up under certain bad conditions.
    ECON1bits.TXRST = 1;
    ECON1bits.TXRST = 0;

    // Wait at least 1.6us after TX Reset before setting TXRTS.
    // If you don't wait long enough, the TX logic won't be finished resetting.
    {volatile BYTE i = 8; while(i--);}
    EIRbits.TXERIF = 0;

    // Start the transmission
    // After transmission completes (MACIsTxReady() returns TRUE), the packet
    // can be modified and transmitted again by calling MACFlush() again.
    // Until MACPutHeader() is called, the data in the TX buffer will not be
    // corrupted.
    ECON1bits.TXRTS = 1;
    wTXWatchdog = TickGet();
}


/******************************************************************************
 * Function:        void MACSetReadPtrInRx(WORD offset)
 *
 * PreCondition:    A packet has been obtained by calling MACGetHeader() and
 *		    getting a TRUE result.
 *
 * Input:           offset: WORD specifying how many bytes beyond the Ethernet
 *			    header's type field to relocate the SPI read
 *			    pointer.
 *
 * Output:          None
 *
 * Side Effects:    None
 *
 * Overview:        SPI read pointer are updated.  All calls to
 *		    MACGet() and MACGetArray() will use these new values.
 *
 * Note:	    RXSTOP must be statically defined as being > RXSTART for
 *		    this function to work correctly.  In other words, do not
 *		    define an RX buffer which spans the 0x1FFF->0x0000 memory
 *		    boundary.
 *****************************************************************************/
void MACSetReadPtrInRx(WORD offset)
{
    WORD_VAL ReadPT;

    // Determine the address of the beginning of the entire packet
    // and adjust the address to the desired location
    ReadPT.Val = CurrentPacketLocation.Val + sizeof(ENC_PREAMBLE) + offset;

    // Since the receive buffer is circular, adjust if a wraparound is needed
    if(ReadPT.Val > RXSTOP)  ReadPT.Val -= RXSIZE;

    // Set the read pointer to the new calculated value
    ERDPTL = ReadPT.v[0];
    ERDPTH = ReadPT.v[1];
}


/******************************************************************************
 * Function:        WORD MACSetWritePtr(WORD Address)
 *
 * PreCondition:    None
 *
 * Input:           Address: Address to seek to
 *
 * Output:          WORD: Old EWRPT location
 *
 * Side Effects:    None
 *
 * Overview:        SPI write pointer is updated.  All calls to
 *		    MACPut() and MACPutArray() will use this new value.
 *
 * Note:	    None
 *****************************************************************************/
WORD MACSetWritePtr(WORD address)
{
    WORD oldVal;

    oldVal = ((WORD)EWRPTH)<<8|EWRPTL;

    EWRPTL = LOW(address);
    EWRPTH = HIGH(address);
    return oldVal;
}

/******************************************************************************
 * Function:        WORD MACSetReadPtr(WORD Address)
 *
 * PreCondition:    None
 *
 * Input:           Address: Address to seek to
 *
 * Output:          WORD: Old ERDPT value
 *
 * Side Effects:    None
 *
 * Overview:        SPI write pointer is updated.  All calls to
 *		    MACPut() and MACPutArray() will use this new value.
 *
 * Note:			None
 *****************************************************************************/
WORD MACSetReadPtr(WORD address)
{
    WORD oldVal;

    oldVal = ((WORD)ERDPTH)<<8|ERDPTL;
    ERDPTL = LOW(address);
    ERDPTH = HIGH(address);
    return oldVal;
}


/******************************************************************************
 * Function:        WORD MACCalcRxChecksum(WORD offset, WORD len)
 *
 * PreCondition:    None
 *
 * Input:           offset   - Number of bytes beyond the beginning of the
 *			       Ethernet data (first byte after the type field)
 *			       where the checksum should begin
 *		    len	     - Total number of bytes to include in the checksum
 *
 * Output:          16-bit checksum as defined by RFC 793.
 *
 * Side Effects:    None
 *
 * Overview:        This function performs a checksum calculation in the MAC
 *                  buffer itself
 *
 * Note:            None
 *****************************************************************************/
WORD MACCalcRxChecksum(WORD offset, WORD len)
{
    WORD temp;
    WORD RDSave;

    // Add the offset requested by firmware plus the Ethernet header
    temp = CurrentPacketLocation.Val + sizeof(ENC_PREAMBLE) + offset;
    if(temp > RXSTOP)		// Adjust value if a wrap is needed
    {
	temp -= RXSIZE;
    }

    RDSave = ((WORD)ERDPTH)<<8|ERDPTL;
    ERDPTL = LOW(temp);
    ERDPTH = HIGH(temp);
    temp = CalcIPBufferChecksum(len);
    ERDPTL = LOW(RDSave);
    ERDPTH = HIGH(RDSave);

    return temp;
}


/******************************************************************************
 * Function:        WORD CalcIPBufferChecksum(WORD len)
 *
 * PreCondition:    Read buffer pointer set to starting of checksum data
 *
 * Input:           len: Total number of bytes to calculate the checksum over.
 *			 The first byte included in the checksum is the byte
 *			 pointed to by ERDPT, which is updated by calls to
 *			 MACGet(), MACSetRxBuffer(), MACSetTxBuffer(), etc.
 *
 * Output:          16-bit checksum as defined by RFC 793
 *
 * Side Effects:    None
 *
 * Overview:        This function performs a checksum calculation in the MAC
 *                  buffer itself.  The MAC has a hardware DMA module
 *		    which can calculate the checksum faster than software, so
 *		    this function replaces the CaclIPBufferChecksum() function
 *		    defined in the helpers.c file.  Through the use of
 *		    preprocessor defines, this replacement is automatic.
 *
 * Note:            This function works either in the RX buffer area or the TX
 *		    buffer area.  No validation is done on the len parameter.
 *****************************************************************************/
/*
WORD CalcIPBufferChecksum(WORD len)
{
    WORD_VAL temp;

    // Take care of special cases which the DMA cannot be used for
    if(len == 0u)
    {
	return 0xFFFF;
    }
    else if(len == 1u)
    {
	return ~((WORD)MACGet());
    }


    // Set the DMA starting address to the RAM read pointer value
    temp.Val = (WORD)ERDPTH)<<8|ERDPTL;
    EDMASTL = LOW(temp.Val);
    EDMASTH = HIGH(temp.Val);

    // See if we are calculating a checksum within the RX buffer (where
    // wrapping rules apply) or TX/unused area (where wrapping rules are
    // not applied)
#if RXSTART == 0
    if(temp.Val <= RXSTOP)
#else
    if(temp.Val >= RXSTART && temp.Val <= RXSTOP)
#endif
    {
	// Calculate the DMA ending address given the starting address and len
	// parameter. The DMA will follow the receive buffer wrapping boundary.
	temp.Val += len-1;
	if(temp.Val > RXSTOP)
	{
	    temp.Val -= RXSIZE;
	}
    }
    else
    {
	temp.Val += len-1;
    }

    // Write the DMA end address
    EDMANDL = LOW(temp.Val);
    EDMANDH = HIGH(temp.Val);

    // Begin the DMA checksum calculation and wait until it is finished
    ECON1bits.CSUMEN = 1;
    ECON1bits.DMAST = 1;
    while(ECON1bits.DMAST);

    // Return the resulting good stuff
    return (((WORD)EDMACSH)<<8) | EDMACSL;
}
*/

/******************************************************************************
 * Function:        WORD CalcIPBufferChecksum(WORD len)
 *
 * PreCondition:    Read buffer pointer set to starting of checksum data
 *
 * Input:           len: Total number of bytes to calculate the checksum over.
 *			 The first byte included in the checksum is the byte
 *			 pointed to by ERDPT, which is updated by calls to
 *			 MACSetReadPtr(), MACGet(), MACGetArray(),
 *			 MACGetHeader(), etc.
 *
 * Output:          16-bit checksum as defined by RFC 793
 *
 * Side Effects:    None
 *
 * Overview:        This function performs a checksum calculation in the MAC
 *                  buffer itself
 *
 * Note:            This function works either in the RX buffer area or the TX
 *		    buffer area.  No validation is done on the len parameter.
 *****************************************************************************/
WORD CalcIPBufferChecksum(WORD len)
{
    WORD Start;
    DWORD_VAL Checksum = {0x00000000ul};
    WORD ChunkLen;
    BYTE DataBuffer[20];	// Must be an even size
    WORD *DataPtr;

    // Save the read pointer starting address
    Start = ((WORD)ERDPTH)<<8|ERDPTL;

    while(len)
    {
	// Obtain a chunk of data (less SPI overhead compared
	// to requesting one byte at a time)
	ChunkLen = len > sizeof(DataBuffer) ? sizeof(DataBuffer) : len;
	MACGetArray(DataBuffer, ChunkLen);

	len -= ChunkLen;

	// Take care of a last odd numbered data byte
	if(((WORD_VAL*)&ChunkLen)->bits.b0)
	{
	    DataBuffer[ChunkLen] = 0x00;
	    ChunkLen++;
	}

	// Calculate the checksum over this chunk
	DataPtr = (WORD*)&DataBuffer[0];
	while(ChunkLen)
	{
	    Checksum.Val += *DataPtr++;
	    ChunkLen -= 2;
	}
    }

    // Restore old read pointer location
    ERDPTL = LOW(Start);
    ERDPTH = HIGH(Start);

    // Do an end-around carry (one's complement arrithmatic)
    Checksum.Val = (DWORD)Checksum.w[0] + (DWORD)Checksum.w[1];

    // Do another end-around carry in case if the prior add
    // caused a carry out
    Checksum.w[0] += Checksum.w[1];

    // Return the resulting checksum
    return ~Checksum.w[0];
}

/******************************************************************************
 * Function:     void MACMemCopyAsync(WORD destAddr, WORD sourceAddr, WORD len)
 *
 * PreCondition:    None
 *
 * Input:           destAddr:	Destination address in the Ethernet memory to
 *				copy to.  If the MSb is set, the current EWRPT
 *				value will be used instead.
 *		    sourceAddr:	Source address to read from.  If the MSb is
 *				set, the current ERDPT value will be used
 *				instead.
 *		    len:	Number of bytes to copy
 *
 * Output:          None
 *
 * Side Effects:    None
 *
 * Overview:        Bytes are asynchrnously transfered within the buffer.  Call
 *		    MACIsMemCopyDone() to see when the transfer is complete.
 *
 * Note:            If a prior transfer is already in progress prior to
 *		    calling this function, this function will block until it
 *		    can start this transfer.
 *****************************************************************************/
void MACMemCopyAsync(WORD destAddr, WORD sourceAddr, WORD len)
{
    WORD_VAL ReadSave, WriteSave;
    BOOL UpdateWritePointer = FALSE;
    BOOL UpdateReadPointer = FALSE;

    if(((WORD_VAL*)&destAddr)->bits.b15)
    {
	UpdateWritePointer = TRUE;
	destAddr = ((WORD)EWRPTH)<<8|EWRPTL;
    }
    if(((WORD_VAL*)&sourceAddr)->bits.b15)
    {
	UpdateReadPointer = TRUE;
	sourceAddr = ((WORD)ERDPTH)<<8|ERDPTL;
    }

    // Handle special conditions where len == 0 or len == 1
    // The DMA module is not capable of handling those corner cases
    if(len <= 1u)
    {
	ReadSave.Val = ((WORD)ERDPTH)<<8|ERDPTL;
	WriteSave.Val =  ((WORD)EWRPTH)<<8|EWRPTL;
	ERDPTL = LOW(sourceAddr);
	ERDPTH = HIGH(sourceAddr);
	EWRPTL = LOW(destAddr);
	EWRPTH = HIGH(destAddr);
	while(len--) MACPut(MACGet());
	if(!UpdateReadPointer)
	{
	    ERDPTL = LOW(ReadSave.Val);
	    ERDPTH = HIGH(ReadSave.Val);
	}
	if(!UpdateWritePointer)
	{
	    EWRPTL = LOW(WriteSave.Val);
	    EWRPTH = HIGH(WriteSave.Val);
	}
    }
    else
    {
	if(UpdateWritePointer)
	{
	    WriteSave.Val = destAddr + len;
	    EWRPTL = LOW(WriteSave.Val);
	    EWRPTH = HIGH(WriteSave.Val);
	}
	len += sourceAddr - 1;
	while(ECON1bits.DMAST);
	EDMASTL = LOW(sourceAddr);
	EDMASTH = HIGH(sourceAddr);
	EDMADSTL = LOW(destAddr);
	EDMADSTH = HIGH(destAddr);
	if((sourceAddr <= RXSTOP) && (len > RXSTOP))
	    len -= RXSIZE; //it is a circular buffer
	EDMANDL = LOW(len);
	EDMANDH = HIGH(len);
	ECON1bits.CSUMEN = 0;
	ECON1bits.DMAST = 1;
	while(ECON1bits.DMAST);	// DMA requires that you must not 
	                        //access EDATA while DMA active

	if(UpdateReadPointer)
	{
	    len++;
	    if((sourceAddr <= RXSTOP) && (len > RXSTOP))
	        len -= RXSIZE;
	    ERDPTL = LOW(len);
	    ERDPTH = HIGH(len);
	}
    }
}

/*
void MACMemCopyAsync(WORD destAddr, WORD sourceAddr, WORD len)
{
    WORD_VAL ReadSave, WriteSave;
    BOOL UpdateWritePointer = FALSE;
    BOOL UpdateReadPointer = FALSE;

    if(((WORD_VAL*)&destAddr)->bits.b15)
    {
	UpdateWritePointer = TRUE;
	destAddr = ((WORD)EWRPTH)<<8|EWRPTL;
    }
    if(((WORD_VAL*)&sourceAddr)->bits.b15)
    {
	UpdateReadPointer = TRUE;
	sourceAddr = ((WORD)ERDPTH)<<8|ERDPTL;
    }

    ReadSave.Val = ((WORD)ERDPTH)<<8|ERDPTL;
    WriteSave.Val =  ((WORD)EWRPTH)<<8|EWRPTL;
    ERDPTL = LOW(sourceAddr);
    ERDPTH = HIGH(sourceAddr);
    EWRPTL = LOW(destAddr);
    EWRPTH = HIGH(destAddr);
    while(len--)
    {
	MACPut(MACGet());
    }

    if(!UpdateReadPointer)
    {
	ERDPTL = LOW(ReadSave.Val);
	ERDPTH = HIGH(ReadSave.Val);
    }
    if(!UpdateWritePointer)
    {
	EWRPTL = LOW(WriteSave.Val);
	EWRPTH = HIGH(WriteSave.Val);
    }
}
*/

BOOL MACIsMemCopyDone(void)
{
    return !ECON1bits.DMAST;
}

/******************************************************************************
 * Function:        BYTE MACGet()
 *
 * PreCondition:    ERDPT must point to the place to read from.
 *
 * Input:           None
 *
 * Output:          Byte read from the Ethernet's buffer RAM
 *
 * Side Effects:    None
 *
 * Overview:        MACGet returns the byte pointed to by ERDPT and
 *		    increments ERDPT so MACGet() can be called again.  The
 *		    increment will follow the receive buffer wrapping boundary.
 *
 * Note:            For better performance, implement this function as a macro:
 *		    #define MACGet()	(EDATA)
 *****************************************************************************/
BYTE MACGet()
{
    return EDATA;
}//end MACGet


/******************************************************************************
 * Function:        WORD MACGetArray(BYTE *val, WORD len)
 *
 * PreCondition:    ERDPT must point to the place to read from.
 *
 * Input:           *val: Pointer to storage location; if val is a null pointer
 *                        read data are discarded
 *		    len:  Number of bytes to read from the data buffer.
 *
 * Output:          Byte(s) of data read from the data buffer.
 *
 * Side Effects:    None
 *
 * Overview:        Reads several sequential bytes from the data buffer
 *		    and places them into local memory.  ERDPT is incremented
 *		    after each byte, following the same rules as MACGet().
 *
 * Note:            None
 *****************************************************************************/
WORD MACGetArray(BYTE *val, WORD len)
{
    WORD w;
    volatile BYTE i;

    w = len;
    if(val)
    {
        while(w--)
        {
	    *val++ = EDATA;
        }
    }
    else
    {
	while(w--)
	{
	    i = EDATA;
	}
    }

    return len;
}//end MACGetArray


/******************************************************************************
 * Function:        void MACPut(BYTE val)
 *
 * PreCondition:    EWRPT must point to the location to begin writing.
 *
 * Input:           Byte to write into the Ethernet buffer memory
 *
 * Output:          None
 *
 * Side Effects:    None
 *
 * Overview:        Writes to the EDATA register, which will indirectly
 *					increment EWRPTH:EWRPTL.
 *
 * Note:            For better performance, implement this function as a macro:
 *		    #define MACPut(val)	EDATA = val;
 *****************************************************************************/
void MACPut(BYTE val)
{
  /* nonsense: ERDPT and EWRPT are auto increment pointers that are simply
   * updated at each access: only write-after-read instructions increment both
   * but here, we just write!
	// Note:  Due to a PIC18F97J60 bug, you must use the MOVFF instruction
	// to write to EDATA or else the read pointer (ERDPT) will 
	// inadvertently increment.
	PRODL = val;
	#if defined(HI_TECH_C)
		asm("movff	_PRODL, _EDATA");
        #elif defined(__SDCC__)
		_asm movff	_PRODL, _EDATA _endasm;
	#else
		_asm movff	PRODL, EDATA _endasm;
	#endif
  */
  EDATA = val;
}//end MACPut


/******************************************************************************
 * Function:        void MACPutArray(BYTE *val, WORD len)
 *
 * PreCondition:    EWRPT must point to the location to begin writing.
 *
 * Input:           *val: Pointer to source of bytes to copy.
 *		    len:  Number of bytes to write to the data buffer.
 *
 * Output:          None
 *
 * Side Effects:    None
 *
 * Overview:        MACPutArray writes several sequential bytes to the
 *		    Ethernet buffer RAM.  It performs faster than multiple 
 *		    MACPut() calls.  EWRPT is incremented by len.
 *
 * Note:            For better performance, implement this function as a macro:
 *		    #define MACPutArray(val,len)  do{\
 *                      BYTE* myval = val;\
 *                      WORD mylen = len;\
 *                      while(mylen--) EDATA = *myval++;\
 *                  }while(0);    
 *
 *****************************************************************************/
void MACPutArray(BYTE *val, WORD len)
{
    while(len--)
    {

/*nonsense; see comment in MACPut
	// Note: Due to a PIC18F97J60 bug, you must use the MOVFF instruction
	// to write to EDATA or else the read pointer (ERDPT) will 
	// inadvertently increment.
	PRODL = *val++;
	#if defined(HI_TECH_C)
	    asm("movff	_PRODL, _EDATA");
        #elif defined(__SDCC__)
	    _asm movff	_PRODL, _EDATA _endasm;
	#else
	    _asm movff	PRODL, EDATA _endasm;
	#endif
*/
        EDATA = *val++; 
    }
}//end MACPutArray

/*ML
void MACPutROMArray(ROM BYTE *val, WORD len)
{
    while(len--)
    {
	// Note: Due to a PIC18F97J60 bug, you must use the MOVFF instruction
	// to write to EDATA or else the read pointer (ERDPT) will 
	// inadvertently increment.
	PRODL = *val++;
	#if defined(HI_TECH_C)
	    asm("movff	_PRODL, _EDATA");
        #elif defined(__SDCC__)
	    _asm movff	_PRODL, _EDATA _endasm;
	#else
	    _asm movff	PRODL, EDATA _endasm;
	#endif
    }
}//end MACPutROMArray
ML*/

/******************************************************************************
 * Function:        ReadPHYReg
 *
 * PreCondition:    Ethernet module must be enabled (ECON1.ETHEN = 1).
 *
 * Input:           Address of the PHY register to read from.
 *
 * Output:          16 bits of data read from the PHY register.
 *
 * Side Effects:    None
 *
 * Overview:        ReadPHYReg performs an MII read operation.  While in
 *		    progress, it simply polls the MII BUSY bit wasting time
 *		    (10.24us).
 *
 * Note:            None
 *****************************************************************************/
WORD ReadPHYReg(BYTE Register)
{
    PHYREG Result;
    WORD Result2;

    // Set the right address and start the register read operation
    MIREGADR = Register; Nop();
    MICMD = MICMD_MIIRD; Nop();

    // Loop to wait until the PHY register has been read through the MII
    // This requires 10.24us
    while(MISTATbits.BUSY);

    // Stop reading
    MICMD = 0x00; Nop();

    // Obtain results and return
    Result.VAL.v[0] = MIRDL;
    Nop();
    Result.VAL.v[1] = MIRDH;
    Result2 =  Result.Val;
    return Result2;
}//end ReadPHYReg


/******************************************************************************
 * Function:        WritePHYReg
 *
 * PreCondition:    Ethernet module must be enabled (ECON1.ETHEN = 1).
 *
 * Input:           Address of the PHY register to write to.
 *		    16 bits of data to write to PHY register.
 *
 * Output:          None
 *
 * Side Effects:    None
 *
 * Overview:        WritePHYReg performs an MII write operation.  While in
 *		    progress, it simply polls the MII BUSY bit wasting time
 *					(10.24us).
 *
 * Note:            None
 *****************************************************************************/
void WritePHYReg(BYTE Register, WORD Data)
{
    BYTE GIESave;

    // Write the register address
    MIREGADR = Register;

    // Write the data through the MIIM interface
    // Order is important: write low byte first, high byte last
    //
    // Due to a silicon problem, you can't access any register with LSb address
    // bits of 0x16 between your write to MIWRL and MIWRH or else the value in
    // MIWRL will be corrupted.  This inline assembly prevents this by copying
    // the value to PRODH:PRODL first, which is at fixed locations of
    // 0xFF4:0xFF3.  These addresses have LSb address bits of 0x14 and 0x13.
    // Interrupts must be disabled to prevent arbitrary ISR code from accessing
    // memory with LSb bits of 0x16 and corrupting the MIWRL value.
    PRODL = ((WORD_VAL*)&Data)->v[0];
    PRODH = ((WORD_VAL*)&Data)->v[1];
    GIESave = INTCON & 0xC0;	 // Save GIEH and GIEL bits
    INTCON &= 0x3F;		 // Clear INTCONbits.GIEH and INTCONbits.GIEL
    #if defined(HI_TECH_C)
        asm("movff	_PRODL, _MIWRL");
	asm("nop");
	asm("movff	_PRODH, _MIWRH");
    #elif defined(__SDCC__)
	_asm
	movff	_PRODL, _MIWRL
	nop
	movff	_PRODH, _MIWRH
	_endasm;
    #else
	_asm
	movff	PRODL, MIWRL
	nop
	movff	PRODH, MIWRH
	_endasm;
    #endif
    INTCON |= GIESave;		       // Restore GIEH and GIEL value

    // Wait until the PHY register has been written
    // This operation requires 10.24us
    while(MISTATbits.BUSY);
}//end WritePHYReg


/******************************************************************************
 * Function:        void MACPowerDown(void)
 *
 * PreCondition:    None
 *
 * Input:           None
 *
 * Output:          None
 *
 * Side Effects:    None
 *
 * Overview:        MACPowerDown disables the Ethernet module.
 *		    All MAC and PHY registers should not be accessed.
 *
 * Note:            Normally, this function would be called before putting the
 *		    PIC to sleep.  If a packet is being transmitted while this
 *		    function is called, this function will block until it is
 *		    it complete. If anything is being received, it will be
 *		    completed.
 *
 *		    The Ethernet module will continue to draw significant
 *		    power in sleep mode if this function is not called first.
 *****************************************************************************/
void MACPowerDown(void)
{
    // Disable packet reception
    ECON1bits.RXEN = 0;

    // Make sure any last packet which was in-progress when RXEN was cleared
    // is completed
    while(ESTATbits.RXBUSY);

    // If a packet is being transmitted, wait for it to finish
    while(ECON1bits.TXRTS);

    // Disable the Ethernet module
    ECON2bits.ETHEN = 0;
}//end MACPowerDown

/******************************************************************************
 * Function:        void MACPowerUp(void)
 *
 * PreCondition:    None
 *
 * Input:           None
 *
 * Output:          None
 *
 * Side Effects:    None
 *
 * Overview:        MACPowerUp returns the Ethernet module back to normal 
 *		    operationafter a previous call to MACPowerDown(). Calling 
 *		    this function when already powered up will have no effect.
 *
 * Note:            If a link partner is present, it will take 10s of
 *		    milliseconds before a new link will be established after
 *		    waking up.  While not linked, packets which are
 *		    transmitted will most likely be lost.  MACIsLinked() can
 *		    be called to determine if a link is established.
 *****************************************************************************/
void MACPowerUp(void)
{
    // Power up the Ethernet module
    ECON2bits.ETHEN = 1;

    // Wait for PHY to become ready
    while(!ESTATbits.PHYRDY)

    // Enable packet reception
    ECON1bits.RXEN = 1;
}//end MACPowerUp



/******************************************************************************
 * Function:        void SetRXHashTableEntry(MAC_ADDR DestMACAddr)
 *
 * PreCondition:    SPI bus must be initialized (done in MACInit()).
 *
 * Input:           DestMACAddr: 6 byte group destination MAC address to allow
 *		     through the Hash Table Filter
 *
 * Output:          Sets the appropriate bit in the EHT* registers to allow
 *		    packets sent to DestMACAddr to be received if the Hash
 *		    Table receive filter is enabled
 *
 * Side Effects:    None
 *
 * Overview:        Calculates a CRC-32 using polynomial 0x4C11DB7 and then,
 *		    using bits 28:23 of the CRC, sets the appropriate bit in
 *		    the EHT* registers
 *
 * Note:            This code is commented out to save code space on systems
 *		    that do not need this function.  Change the "#if 0" line
 *		    to "#if 1" to uncomment it.
 *****************************************************************************/
#if 0
void SetRXHashTableEntry(MAC_ADDR DestMACAddr)
{
    DWORD_VAL CRC = {0xFFFFFFFF};
    BYTE *HTRegister;
    BYTE i, j;

    // Calculate a CRC-32 over the 6 byte MAC address
    // using polynomial 0x4C11DB7
    for(i = 0; i < sizeof(MAC_ADDR); i++)
    {
	BYTE  crcnext;

	// shift in 8 bits
	for(j = 0; j < 8; j++)
	{
	    crcnext = 0;
	    if(((BYTE_VAL*)&(CRC.v[3]))->bits.b7)
	    crcnext = 1;
	    crcnext ^= (((BYTE_VAL*)&DestMACAddr.v[i])->bits.b0);

	    CRC.Val <<= 1;
	    if(crcnext)
	    CRC.Val ^= 0x4C11DB7;
	    // next bit
	    DestMACAddr.v[i] >>= 1;
	}
    }

    // CRC-32 calculated, now extract bits 28:23
    // Bits 25:23 define where within the Hash Table byte the bit needs to be
    //            set 
    // Bits 28:26 define which of the 8 Hash Table bytes that bits 25:23 apply 
    //            to
    i = CRC.v[3] & 0x1F;
	HTRegister = (i >> 2) + &EHT0;
	i = (i << 1) & 0x06;
	((BYTE_VAL*)&i)->bits.b0 = ((BYTE_VAL*)&CRC.v[2])->bits.b7;

	// Set the proper bit in the Hash Table
	*HTRegister |= 1<<i;
}
#endif
/******************************************************************************
 * Function:        void MACPrintHeader(BYTE x)
 *
 * PreCondition:    None
 *
 * Input            x: position in words in the header from where to print
 *
 *
 * Side Effects:   The header in the send buffer is printed in headecimal 
 *                 starting from byte x
 *
 * Overview:        None
 *
 * Note:            this is a debugging function, not to use in operational
 *                  code
 *****************************************************************************/
void MACPrintHeader(BYTE woffset)
{
    BYTE header[64];
    BYTE i, c;

    // Set the read pointer to the beginning of the transmit buffer
    ERDPTL = LOW(TXSTART + 1) ;
    ERDPTH = HIGH(TXSTART + 1) ;
 
    // Obtain the MAC header from the Ethernet buffer
    MACGetArray((BYTE*)&header[0], sizeof(header));

    for(i=0; i<16; i++)
    {
        c = (header[i+2*woffset]>>4)&0x0F;
        LCDText[2*i] = c + (c < 0x0A ? '0' : 'A');
	c = header[i+2*woffset]&0x0F;
        LCDText[2*i+1] = c + (c < 0x0A ? '0' : 'A');
    }
    LCDText[32]=0;

    LCDUpdate();

}



#endif //#if (defined(__18F97J60) || defined(__18F96J65) || 
       //defined(__18F96J60) || defined(__18F87J60) || defined(__18F86J65) || 
       //defined(__18F86J60) || defined(__18F67J60) || defined(__18F66J65) || 
       //defined(__18F66J60)) || defined(HI_TECH_C)
